Introduction.

\noindent Whenever a programmer wants to change a given \.{WEB program because of system dependencies, he will create a new change file. In addition there may be a second change file to modify system independent modules of the program. But the \.{WEB file cannot be tangled and weaved with more than one change file simultaneously. Therefore, we introduce the present program to merge a \.{WEB file and several change files producing a new \.{WEB file. Since the input files are tied together, the program is called \.{TIE. Furthermore, the program can be used to merge several change files giving a new single change file. This method seems to be more important because it doesn’t modify the original source file. The use of \.{TIE can be expanded to other programming languages since this processor only knows about the structure of change files and does not interpret the master file at all.

The program \.{TIE has to read lines from several input files to bring them in some special ordering. For this purpose an algorithm is used which looks a little bit complicated. But the method used only needs one buffer line for each input file. Thus the storage requirement of \.{TIE does not depend on the input data.

The program uses only few features of the local \PASCAL\ compiler that may need to be changed in other installations. The changes needed may be similar to those used to change the \.{WEB processors \.{TANGLE and \.{WEAVE. All places where changes may be needed are listed in the index under the entry “system dependencies”. !ŝystem dependencies@>

The “banner line” defined here should be changed whenever \.{TIE is modified. This program is put into the public domain. Nevertheless the copyright notice must not be replaced or modified.

@d banner==’This is TIE, Version 1.3’ @d copyright== ’Copyright (c) 1983, 1984, 1986 by THD/ITI. All rights reserved.’

 If it is necessary to abort the job because of a fatal error, the program calls the ‘|jump_out|’ procedure, which goes to the label |end_of_TIE|.

@d end_of_TIE = 9999 {go here to wrap it up

@p @t\4@>@<Compiler directives@> program TIE(out_file); label end_of_TIE; {go here to finish const @<Constants in the outer block@>@; type @<Types in the outer block@>@; var @<Globals in the outer block@>@; @t\4@>@<Error handling procedures@>@; procedure initialize; var @<Local variables for initialization@>@; begin @<Set initial values@> end;

 Some of this code ist optional for use when debugging only; such material is enclosed between the delimiters |debug| and |gubed|.

@d debug==@t@>{ {change this to ‘|debug==@t@>|’ when debugging @d gubed==@t@>} {change this to ‘|gubed==@t@>|’ when debugging @f debug==repeat @f gubed==until

 The \PASCAL\ compiler used to develop this system has “compiler directives” that can appear in comments whose first character is a dollar sign. In production versions of \.{TIE these directives tell the compiler that it is safe to avoid range checks and to leave out the extra code it inserts for the \PASCAL\ debugger’s benefit, although interrupts will occur if there is arithmetic overflow. ŝystem dependencies@>

@<Compiler directives@>= {@&$=D-@> } {no range check, catch arithmetic overflow, no debug overhead !debug {@&$=D+@> }@+ gubed @; {but turn everything on when debugging

 Labels are given symbolic names by the following definitions. We insert the label ‘|exit|:’ just before the ‘\ignorespaces|end|\unskip’ of a procedure in which we have used the ‘|return|’ statement defined below. Loops that are set up with the \&{loop construction defined below are commonly exited by going to ‘|done|’ or to ‘|found|’ or to ‘|not_found|’, and they are sometimes repeated by going to ‘|continue|’.

@d exit=10 {go here to leave a procedure @d continue=20 {go here to resume a loop @d done=30 {go here to exit a loop @d found=31 {go here when you’ve found it @d not_found=32 {go here when you’ve found something else

 Here are some macros for common programming idioms.

@d incr(#) == #:=#+1 {increase a variable by unity @d decr(#) == #:=#-1 {decrease a variable by unity @d loop == @+ while true do@+ {repeat over and over until a |goto| happens @d do_nothing == {empty statement @d return == goto exit {terminate a procedure call @f return == nil @f loop == xclause

 We assume that |case| statements may include a default case that applies if no matching label is found. Thus, we shall use constructions like $$ \vbox{ \halign{#\hfil\cr |case x of|\cr \qquad 1: $\langle\,$code for $x=1\,\rangle$;\cr \qquad 3: $\langle\,$code for $x=3\,\rangle$;\cr \qquad |othercases| $\langle\,$code for |x<>1| and |x<>3|$\,\rangle$\cr |endcases|\cr $$ since most \PASCAL\ compilers have plugged this hole in the language by incorporating some sort of default mechanism. For example, the compiler used to develop \.{WEB and \TeX\ allows ‘|others|:’ as a default label, and other \PASCAL s allow syntaxes like ‘\ignorespaces|else|\unskip’ or ‘\&{otherwise’ or ‘\\{otherwise:’, etc. The definitions of |othercases| and |endcases| should be changed to agree with local conventions. (Of course, if no default mechanism is available, the |case| statements of this program must be extended by listing all remaining cases.) ŝystem dependencies@>

@d othercases == others: {default for cases not listed explicitly @d endcases == @+end {follows the default case in an extended |case| statement @f othercases == else @f endcases == end

 The following parameters should be sufficient for most applications of \.{TIE. Note that |max_file_index| should not be enlarged without changing the processing of change file names. ŝystem dependencies@>

@<Constants...@>= !buf_size=100; {maximum length of one input line !max_file_index=9;{we dont think that anyone needs more than 9 change files !max_name_length=54; {adapt this to your local name space

 We introduce a history variable that allows us to set a return code if the operating system and the local \PASCAL\ compiler allow it. First we introduce the coded values for the history.

@d spotless=0 @d troublesome=1 @d fatal=2

@<Globals in ...@>= !history:integer;

 We must initialize this variable at the very beginning.

@<Set initial values@>= history:=spotless;


The character set.

\noindent One of the main goals in the design of \.{TIE has been to make it readily portable between a wide variety of computers. Yet \.{TIE by its very nature must use a greater variety of characters than most computer programs deal with, and character encoding is one of the areas in which existing machines differ most widely from each other.

To resolve this problem, all input to \.{TIE is converted to an internal seven-bit code that is essentially standard \ASCII{, the “American Standard Code for Information Interchange.” The conversion is done immediately when each character is read in. Conversely, characters are converted from \ASCII{ to the user’s external representation just before they are output.

\noindent Here is a table of the standard visible \ASCII{ codes: $$\def\:{\char\count255\global\advance\count255 by 1 \count255=’40 \vbox{ \hbox{\hbox to 40pt{\it\hfill0\/\hfill% \hbox to 40pt{\it\hfill1\/\hfill% \hbox to 40pt{\it\hfill2\/\hfill% \hbox to 40pt{\it\hfill3\/\hfill% \hbox to 40pt{\it\hfill4\/\hfill% \hbox to 40pt{\it\hfill5\/\hfill% \hbox to 40pt{\it\hfill6\/\hfill% \hbox to 40pt{\it\hfill7\/\hfill \vskip 4pt \hrule \def\^{\vrule height 10.5pt depth 4.5pt \halign{\hbox to 0pt{\hskip -24pt\O{#0\hfill&\^ \hbox to 40pt{\tt\hfill#\hfill\^& &\hbox to 40pt{\tt\hfill#\hfill\^\cr 04&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 05&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 06&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 07&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 10&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 11&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 12&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 13&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 14&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 15&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 16&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule 17&\:&\:&\:&\:&\:&\:&\:\cr \hrule width 280pt$$ (Actually, of course, code 0'40 is an invisible blank space.) Code 1'36 was once an upward arrow (\.{\char’13), and code 1'37 was once a left arrow (\.^^X), in olden times when the first draft of \ASCII{ code was prepared; but \.{TIE works with today’s standard \ASCII{ in which those codes represent circumflex and underline as shown.

@<Types...@>= !ascii_code=0..127; {seven-bit numbers, a subrange of the integers

 The original \PASCAL\ compiler was designed in the late 60s, when six-bit character sets were common, so it did not make provision for lowercase letters. Nowadays, of course, we need to deal with both capital and small letters in a convenient way, so \.{TIE assumes that it is being used with a \PASCAL\ whose character set contains at least the characters of standard \ASCII{ as listed above. Some \PASCAL\ compilers use the original name |char| for the data type associated with the characters in text files, while other \PASCAL s consider |char| to be a 64-element subrange of a larger data type that has some other name.

In order to accommodate this difference, we shall use the name |text_char| to stand for the data type of the characters in the input and output files. We shall also assume that |text_char| consists of the elements |chr(first_text_char)| through |chr(last_text_char)|, inclusive. The following definitions should be adjusted if necessary. ŝystem dependencies@>

@d text_char == char {the data type of characters in text files @d first_text_char=0 {ordinal number of the smallest element of |text_char| @d last_text_char=127 {ordinal number of the largest element of |text_char|

@<Types...@>= !text_file=packed file of text_char;

 The \.{TIE processor convert between \ASCII{ code and the user’s external character set by means of arrays |xord| and |xchr| that are analogous to \PASCAL’s |ord| and |chr| functions.

@<Globals...@>= !xord: array [text_char] of ascii_code; {specifies conversion of input characters !xchr: array [ascii_code] of text_char; {specifies conversion of output characters

 If we assume that every system using \.{WEB is able to read and write the visible characters of standard \ASCII{ (although not necessarily using the \ASCII{ codes to represent them), the following assignment statements initialize most of the |xchr| array properly, without needing any system-dependent changes. For example, the statement \.{xchr["A"]:=\’A\’ that appears in the present \.{WEB file might be encoded in, say, {\mc EBCDIC code on the external medium on which it resides, but \.{TANGLE will convert from this external code to \ASCII{ and back again. Therefore the assignment statement \.{XCHR[65]:=\’A\’ will appear in the corresponding \PASCAL\ file, and \PASCAL\ will compile this statement so that |xchr[65]| receives the character \.A in the external (|char|) code. Note that it would be quite incorrect to say \.{xchr["A"]:="A", because |"A"| is a constant of type |integer|, not |char|, and because we have $|"A"|=65$ regardless of the external character set.

@<Set init...@>= xchr[" "]:=’ ’; xchr["!"]:=’!’; xchr[""""]:=’"’; xchr["#"]:=’#’; xchr["$"]:=’$’; xchr["%"]:=’%’; xchr["&"]:=’&’; xchr["’"]:=””; xchr["("]:=’(’; xchr[")"]:=’)’; xchr["*"]:=’*’; xchr["+"]:=’+’; xchr[","]:=’,’; xchr["-"]:=’-’; xchr["."]:=’.’; xchr["/"]:=’/’; xchr["0"]:=’0’; xchr["1"]:=’1’; xchr["2"]:=’2’; xchr["3"]:=’3’; xchr["4"]:=’4’; xchr["5"]:=’5’; xchr["6"]:=’6’; xchr["7"]:=’7’; xchr["8"]:=’8’; xchr["9"]:=’9’; xchr[":"]:=’:’; xchr[";"]:=’;’; xchr["<"]:=’<’; xchr["="]:=’=’; xchr[">"]:=’>’; xchr["?"]:=’?’; xchr["@"]:=’@’; xchr["A"]:=’A’; xchr["B"]:=’B’; xchr["C"]:=’C’; xchr["D"]:=’D’; xchr["E"]:=’E’; xchr["F"]:=’F’; xchr["G"]:=’G’; xchr["H"]:=’H’; xchr["I"]:=’I’; xchr["J"]:=’J’; xchr["K"]:=’K’; xchr["L"]:=’L’; xchr["M"]:=’M’; xchr["N"]:=’N’; xchr["O"]:=’O’; xchr["P"]:=’P’; xchr["Q"]:=’Q’; xchr["R"]:=’R’; xchr["S"]:=’S’; xchr["T"]:=’T’; xchr["U"]:=’U’; xchr["V"]:=’V’; xchr["W"]:=’W’; xchr["X"]:=’X’; xchr["Y"]:=’Y’; xchr["Z"]:=’Z’; xchr["["]:=’[’; xchr["\"]:=’\’; xchr["]"]:=’]’; xchr["^"]:=’^’; xchr["_"]:=’_’; xchr["‘"]:=’‘’; xchr["a"]:=’a’; xchr["b"]:=’b’; xchr["c"]:=’c’; xchr["d"]:=’d’; xchr["e"]:=’e’; xchr["f"]:=’f’; xchr["g"]:=’g’; xchr["h"]:=’h’; xchr["i"]:=’i’; xchr["j"]:=’j’; xchr["k"]:=’k’; xchr["l"]:=’l’; xchr["m"]:=’m’; xchr["n"]:=’n’; xchr["o"]:=’o’; xchr["p"]:=’p’; xchr["q"]:=’q’; xchr["r"]:=’r’; xchr["s"]:=’s’; xchr["t"]:=’t’; xchr["u"]:=’u’; xchr["v"]:=’v’; xchr["w"]:=’w’; xchr["x"]:=’x’; xchr["y"]:=’y’; xchr["z"]:=’z’; xchr["{"]:=’{’; xchr["|"]:=’|’; xchr[""]:=’’; xchr["~"]:=’~’; xchr[0]:=’ ’; xchr[1'77]:=’ ’; {these \ASCII{ codes are not used

 One of the \ASCII{ codes below 4'0 has been given a symbolic name in \.{TIE because it is used with a special meaning.

@d tab_mark=1'1 {\ASCII{ code used as tab-skip

 When we initialize the |xord| array and the remaining parts of |xchr|, it will be convenient to make use of an index variable, |i|.

@<Local variables for init...@>= !i:0..last_text_char;

 Here now is the system-dependent part of the character set. If \.{TIE is being implemented on a garden-variety \PASCAL\ for which only standard \ASCII{ codes will appear in the input and output files, you don’t need to make any changes here. ŝystem dependencies@>

Changes to the present module will make \.{TIE more friendly on computers that have an extended character set, so that one can type things like \.^^Z. If you have an extended set of characters that are easily incorporated into text files, you can assign codes arbitrarily here, giving an |xchr| equivalent to whatever characters the users of \.{TIE are allowed to have in their input files, provided that unsuitable characters do not correspond to special codes like |carriage_return| that are listed above.

@<Set init...@>= for i:=1 to 3'7 do xchr[i]:=’ ’;

 The following system-independent code makes the |xord| array contain a suitable inverse to the information in |xchr|.

@<Set init...@>= for i:=first_text_char to last_text_char do xord[chr(i)]:=4'0; for i:=1 to 1'76 do xord[xchr[i]]:=i;


Input and output.

\noindent Terminal output is done by writing on file |term_out|, which is assumed to consist of characters of type |text_char|. Terminal input is read from |term_in|. ŝystem dependencies@>

@d print(#)==write(term_out,#) {‘|print|’ means write on the terminal @d print_ln(#)==write_ln(term_out,#) @d new_line==write_ln(term_out) {start new line @d print_nl(#)== @+ begin new_line; print(#) @+ end

@<Globals...@>= !term_in:text_file; !term_out:text_file;

 To initialize terminal input we do a reset, just when the first character is needed. This allows output before the first input line is read even if “lazy input” is not supported by your \PASCAL\ compiler. ŝystem dependencies@>

@<Reset terminal input@>= reset(term_in,’TTY:’,’/I’)

 Similarly we initialize terminal output to a file that should be associated to the user’s terminal interactively. ŝystem dependencies@>

@<Set init...@>= rewrite(term_out,’TTY:’);

 The |update_terminal| procedure is called when we want to make sure that everything we have output to the terminal so far has actually left the computer’s internal buffers and been sent. ŝystem dependencies@>

@d update_terminal == break(term_out) {empty the terminal output buffer

 If an error occurs we indicate this calling a procedure named |err_print|. This procedure is implemented as a macro. It gives a message and an indication of the offending file. The actions to determine the error location will be explained later.

@d error_loc(#) == err_loc(#); history:=troublesome; @+ end @d err_print(#) == @+ begin print_nl(#); error_loc

 The basic procedure |get_ln_from_file| can be used to get a line from an input file. The line is stored in |input_files[i].buffer|. The components |limit| and |line| are updated. If the end of the file is reached |input_files[i].mode| is set to |ignore|. On some systems it might be useful to replace tab characters by a proper number of spaces since several editors used to create change files insert tab characters into a source file not under control of the user. So it might be a problem to create a matching change file. t^ab character expansion@>

We define |get_line| to read a line from a file specified by number. To ease an inplementation without arrays of files we introduce one more parameter. In such cases |get_line| is best replaced by a procedure containing a |case| statement to select the file needed.

@d get_line(#)==get_ln_from_file(#,input_files[#]);

@p procedure get_ln_from_file(i:integer;var cur_file:text_file); label exit; var final_limit:0..buf_size; begin with input_organization[i] do begin if mode=ignore then return; if eof(cur_file) then @<Handle end of file and return@>; @<Get line into buffer@>; end; exit: end;

 End of file is special if this file is the master file.

@<Handle end of file ...@>= begin mode:=ignore; if type_of_file=master then input_has_ended:=true; return; end

 Lines must fit into the buffer completely. Tab character expansion might be done here. t^ab character expansion@>

@<Get line into buffer@>= incr(line); limit:=0; final_limit := 0; while (not eoln(cur_file) and (limit<=buf_size)) do begin buffer[limit]:=xord[cur_file^]; get(cur_file); incr(limit); if (buffer[limit-1]<>" ")and(buffer[limit-1]<>tab_mark) then final_limit:=limit; end; limit:=final_limit; if not eoln(cur_file) then err_print(’! Input line too long’)(i); .Input line too long@> read_ln(cur_file)


Data structures.

\noindent The multiple input files (master file and change files) are treated the same way. To organize the simultaneous usage of several input files, we introduce the data types |in_file_modes|.

The mode |search| indicates that \.{TIE searches for a match of the input line with any line of an input file in |reading| mode. |test| is used whenever a match is found and it has to be tested if the next input lines do match also. |reading| describes that the lines can be read without any check for equality to other lines. |ignore| denotes that the file cannot be used. This may happen because an error has been detected or because the end of the file has been found.

\leavevmode |file_types| is used to describe whether a file is a master file or a change file.

@<Types...@>= !in_file_modes=(!search,!test,!reading,!ignore); !file_types=(!master,!chf);

 The following data structures join all informations needed to use these input files.

@<Types...@>= !out_md_type=(!normal,!pre,!post); !name_type=record !name:array[1..max_name_length] of char; !nam_len:0..max_name_length; end; !buffer_index=0..buf_size; !file_index=0..max_file_index; !input_description=record !name_of_file: name_type; !buffer: array[0..buf_size]of ascii_code; !mode: in_file_modes; !line: integer; !type_of_file: file_types; !limit: buffer_index; end;

 These data types are used in the global variable section.

@<Globals...@>= !actual_input,!test_input:integer; !input_has_ended:boolean; !input_organization: array[0..max_file_index] of input_description; !input_files: array[0..max_file_index] of text_file; !no_ch,!cmd:integer; !c:char; i,j:integer; !prod_chf:boolean; !name_field:name_type; !master_ext:name_type; !cf_ext:name_type; !no_ext:name_type; !out_mode:out_md_type; !ext_start:0..max_name_length;


Reporting errors to the user.

\noindent There may be errors if a line in a given change file does not match a line in the master file or a replacement in a previous change file. Such errors are reported to the user by saying $$ \hbox{|err_print(’! Error message’)|. $$ Please note that no trailing point is supplied by the error message because it is appended by |err_print|. Non recoverable errors are handled by calling |fatal_error| that outputs a message and then calls ‘|jump_out|’.

\leavevmode |err_print| will print the error message followed by an indication of where the error was spotted in the source files. |fatal_error| cannot state any files because the problem is usually to access these.

For |err_print| messages the following procedure is used to write the proper name of an input file.

@d fatal_error(#)==begin print_nl(#); print_ln(’.’); history:=fatal; jump_out; end

@<Error handling...@>= procedure print_name_of_file(cur_index:file_index); var i:integer; begin with input_organization[cur_index] do for i:=1 to name_of_file.nam_len do print(name_of_file.name[i]); end;

 The actual error indications are provided by a procedure called |err_loc|.

@<Error handling...@>= procedure err_loc(i:integer); {prints location of error begin with input_organization[i] do begin print(’ (file ’); print_name_of_file(i); print(’, l.’,line:1,’).’); new_line; end; end;

 The |jump_out| procedure just cuts across all active procedure levels and jumps out of the program. This is the only non-local |goto| statement in \.{TIE. It is used when no recovery from a particular error has been provided.

Some \PASCAL\ compilers do not implement non-local |goto| statements. In such cases the code that appears at label |end_of_TIE| should be copied into the |jump_out| procedure, followed by a call to a system procedure that terminates the program. ŝystem dependencies@>

@<Error handling...@>= procedure jump_out; begin goto end_of_TIE; end;


Handling multiple change files.

\noindent In the standard version we read the name of the files from the console. Handling of the end of line indicator might need a system dependent update. There might be system dependent changes to get the names of the files from the command line.

If \.{TIE is used on a system which allows filenames only in upper or lower case, |read_chr| should be replaced by a procedure that converts the characters properly. ŝystem dependencies@>

@d read_chr(#)==read(term_in,#)

@p procedure rd_file; var c:char; begin name_field.nam_len:=0; while (not eoln(term_in) and (name_field.nam_len<max_name_length)) do begin incr(name_field.nam_len); read_chr(c); name_field.name[name_field.nam_len]:=c; end; if name_field.nam_len>0 then if not eoln(term_in) then fatal_error(’! File name too long’); .File name too long@> end;

 To do our job we need the master file’s name.

@<Read master file name@>= begin print_nl(’Please enter the name of the master file: ’); .Please enter the name ...@> update_terminal; @<Reset terminal input@>; rd_file; if name_field.nam_len=0 then fatal_error(’! Illegal file name’); .Illegal file name@> @<Ignore master file extension@>; if name_field.nam_len+4>max_name_length then fatal_error(’! File name too long’); .File name too long@> end

 The extension of the master source file is to be ignored. We assume that extensions are separated from the base name by a period. Let us scan from the tail to the last period. We skip only characters, digits and underscore characters. Finding any other character stops extension scanning.

@<Ignore master file extension@>= begin if name_field.nam_len>3 then with name_field do begin i:=nam_len; ext_start:=0; {extension not yet found while i>0 do if name[i]=’.’ then begin ext_start:=i; i:=0 end else if (name[i]>=’a’) and (name[i]<=’z’) then i:=i-1 else if (name[i]>=’A’) and (name[i]<=’Z’) then i:=i-1 else if (name[i]>=’0’) and (name[i]<=’9’) then i:=i-1 else if (name[i]=’_’) then i:=i-1 else i:=0; if ext_start>0 then begin {initialize master file extension for i:=ext_start to nam_len do master_ext.name[i-ext_start+1]:=name[i]; master_ext.nam_len:=nam_len-ext_start+1; for i:=master_ext.nam_len+1 to max_name_length do master_ext.name[i]:=’ ’; nam_len:=ext_start-1; end; end end

 We must determine the name of a change file, too. This might be changed to command line parameter processing. ĉommand line processing@>

@<Read change file name@>= begin print_ln(’Please enter the name of change file ’,no_ch:1); print(’or <Return> if there are no more change files: ’); update_terminal; read_ln(term_in); rd_file; end

 The selection whether a new master file or a mixed change file should be produced might be replaced by a command line switch, too. ĉommand line processing@>

@<Change or master file producing@>= repeat print_nl(’Do you want to create a new master or a change file (m/c)? ’); .Do you want to create ...@> update_terminal; read_ln(term_in); read_chr(c); cmd:=xord[c]; if (cmd>="a") and (cmd<="z") then cmd:=cmd+"A"-"a"; until (cmd="C")or(cmd="M"); prod_chf:=(cmd="C")

 We continue with a function to open a text file. Success is indicated by a boolean result. We assume that empty files can be handled like non existent ones. ŝystem dependencies@>

@p function file_open(var f:text_file;name:name_type):boolean; begin reset(f,name.name); file_open:=not eof(f); end;

 The procedure |insert_name| initializes the name of the file number |cur_index| to “prefix” “extension”.

@p procedure insert_name(cur_index:file_index;prefix:name_type; extension:name_type); var i:integer; begin with input_organization[cur_index] do begin for i:=1 to max_name_length do name_of_file.name[i]:=’ ’; for i:=1 to prefix.nam_len do name_of_file.name[i]:=prefix.name[i]; for i:=1 to extension.nam_len do name_of_file.name[prefix.nam_len+i]:=extension.name[i]; name_of_file.nam_len:=prefix.nam_len+extension.nam_len; end; end;

 @<Get the master file started@>= insert_name(0,name_field,master_ext); if not file_open(input_files[0],input_organization[0].name_of_file) then fatal_error(’! Master file can”t be opened’) .Master file can’t be opened@> else begin print(’(’); print_name_of_file(0); print_ln(’)’); input_organization[0].type_of_file:=master; get_line(0); end;

 This is a procedure to open all input files. Changes might be used to get the names of the files from a command line. In this interactive version \.{TIE will try to use change files with extensions \.{CF1 to \.{CF9 automatically—and there is no way to inhibit this, if those files exist. ĉommand line processing@>

@p @t\4@>@<Procedures for |open_input|@> procedure open_input; {prepare to read |input_files| label done; var i:integer; begin @<Initialize extensions@>; @<Get the master file started@>; actual_input:=0; for i:=1 to max_file_index do begin cf_ext.name[cf_ext.nam_len]:=xchr["0"+i]; insert_name(i,name_field,cf_ext); end; no_ch:=0; while no_ch<9 do begin incr(no_ch); if not file_open(input_files[no_ch], input_organization[no_ch].name_of_file) then begin @<Read change file name@>; if name_field.nam_len=0 then begin decr(no_ch); goto done; end; insert_name(no_ch,name_field,no_ext); decr(no_ch); end else begin print(’(’); print_name_of_file(no_ch); print_ln(’)’); init_change_file(no_ch,true); end; end; done: if no_ch=0 then fatal_error(’! No change file found’); .No change file found@> for i:=no_ch+1 to max_file_index do input_organization[i].mode:=ignore; end;

 According to the names given above, we initialize the extension names. We need the standard change file extensions that are \.{CF$i$ and a “dummy” entry for change files that do not need additional extensions.

@<Initialize extensions@>= cf_ext.name[1]:=’.’; cf_ext.name[2]:=’C’; cf_ext.name[3]:=’F’; cf_ext.name[4]:=’ ’; cf_ext.nam_len:=4; for i:=cf_ext.nam_len+1 to max_name_length do cf_ext.name[i]:=’ ’; no_ext.nam_len:=0; for i:=1 to max_name_length do no_ext.name[i]:=’ ’

 The main output goes to |out_file|.

@<Globals...@>= !out_file:text_file;

 The following code opens |out_file|. Since this file is mentioned in the program header we assume that the \PASCAL\ runtime system has checked the existence of the file. ŝystem dependencies@>

@<Set init...@>= rewrite(out_file);


Input/output organization.

\noindent Here’s a simple function that checks if two lines are different.

@p function lines_dont_match(i,j:integer):boolean; label exit; var k,lmt:buffer_index; begin lines_dont_match:=true; if input_organization[i].limit <> input_organization[j].limit then return; lmt:=input_organization[i].limit; if lmt > 0 then for k:=0 to lmt-1 do if input_organization[i].buffer[k] <> input_organization[j].buffer[k] then return; lines_dont_match:=false; exit: end;

 Procedure |init_change_file(i,b)| is used to ignore all lines of the input file with index |i| until the next change module is found. The boolean parameter |b| indicates whether we do not want to see ’@x’ or ’@y’ entries during our skip.

@<Procedures for |open_input|@>= procedure init_change_file(i:integer;b:boolean); label continue, done, exit; begin with input_organization[i] do begin @<Skip over comment lines; |return| if end of file@>; @<Skip to the next nonblank line; |return| if end of file@>; end; exit: end;

 While looking for a line that begins with \.{@x in the change file, we allow lines that begin with \.{@, as long as they don’t begin with \.{@y or \.{@z (which would probably indicate that the change file is fouled up).

@<Skip over comment lines...@>= loop@+ begin get_line(i); if mode=ignore then return; if limit<2 then goto continue; if buffer[0] <> "@" then goto continue; if (buffer[1]>="X") and (buffer[1]<="Z") then buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify if buffer[1]="x" then goto done; if (buffer[1]="y") or (buffer[1]="z") then if b then {waiting for start of change err_print(’! Where is the matching @x?’)(i); .Where is the match...@> continue: end; done:

 Here we are looking at lines following the \.{@x.

@<Skip to the next nonblank line...@>= repeat get_line(i); if mode=ignore then begin err_print(’! Change file ended after @x’)(i); .Change file ended...@> return; end; until limit>0

 The |put_line| procedure is used to write a line from input buffer |j| to the output file.

@p procedure put_line(j:integer); var i:integer; {index into the buffer begin with input_organization[j] do for i:=0 to limit-1 do write(out_file,xchr[buffer[i]]); write_ln(out_file); end;

 The function |e_of_ch_module| returns true if the input line from file |i| is equal to \.{@z.

@p function e_of_ch_module(i:integer):boolean; begin e_of_ch_module:=false; with input_organization[i] do if limit>=2 then if buffer[0]="@" then if (buffer[1]="Z") or (buffer[1]="z") then e_of_ch_module:=true; end;

 The function |e_of_ch_preamble| returns true if the input line from file |i| is equal to \.{@y.

@p function e_of_ch_preamble(i:integer):boolean; begin e_of_ch_preamble:=false; with input_organization[i] do if limit>=2 then if buffer[0]="@" then if (buffer[1]="Y") or (buffer[1]="y") then e_of_ch_preamble:=true; end;

 To start with our inputs we set the indication that all change files are in state |search|. That means that we must skip to the next (in this case the first) change entry.

@<Initialize the input system@>= for i:=0 to max_file_index do with input_organization[i] do begin mode:=search; line:=0; type_of_file:=chf; limit:=0; for j:=1 to max_name_length do name_of_file.name[j]:=’ ’; for j:=0 to buf_size do buffer[j]:=0; end; actual_input:=0; out_mode:=normal

 To process the input file the procedure |process_line| reads a line of the actual input file and updates the |input_organization| for all files with index |i| greater |actual_input|.

@p procedure process_line; label done, exit; var i:integer; begin @<Init process@>; @<Handle output@>; @<Step to next line@>; exit: end;

 If |test_input| is |none|, no change file is tested.

@d none == (max_file_index+1)

@<Init process@>= while e_of_ch_module(actual_input) do with input_organization[actual_input] do begin if type_of_file=master then {emergency exit, everything mixed up! fatal_error(’! This can”t happen’); mode:=search; init_change_file(actual_input,true); while ((input_organization[actual_input].mode<>reading) and (actual_input>0)) do decr(actual_input); end; if input_has_ended and (actual_input=0) then return; test_input:=none; i:=actual_input; while ((test_input=none) and (i<no_ch)) do begin incr(i); case input_organization[i].mode of search: if not lines_dont_match(actual_input,i) then begin input_organization[i].mode:=test; test_input:=i; end; test: begin if lines_dont_match(actual_input, i) then begin {error, modules do not match input_organization[i].mode:=search; err_print(’! Sections do not match’)(actual_input); .Sections do not match@> err_loc(i); init_change_file(i,false); end else test_input:=i; end; reading:do_nothing; {this can’t happen ignore: do_nothing; {nothing to do end; end

 @<Handle output@>= if prod_chf then begin loop @+ begin @<Test for normal@>; @<Test for pre@>; @<Test for post@>; end; done: end else if test_input=none then put_line(actual_input)

 Check whether we have to start a change file entry.

@<Test for normal@>= begin if out_mode=normal then if test_input<>none then begin write(out_file,xchr["@"]); write_ln(out_file,xchr["x"]); out_mode:=pre; end else goto done; end

 Check whether we have to start the replacement text.

@<Test for pre@>= begin if out_mode=pre then begin if test_input=none then begin write(out_file,xchr["@"]); write_ln(out_file,xchr["y"]); out_mode:=post; end else begin if input_organization[actual_input].type_of_file=master then put_line(actual_input); goto done; end; end; end

 Check whether a change file entry is complete.

@<Test for post@>= begin if out_mode=post then begin if input_organization[actual_input].type_of_file=chf then begin if test_input=none then put_line(actual_input); goto done; end else begin write(out_file,xchr["@"]); write_ln(out_file,xchr["z"]); write_ln(out_file); out_mode:=normal; end; end; end

 @<Step to next line@>= get_line(actual_input); if test_input<>none then begin get_line(test_input); if e_of_ch_preamble(test_input) then begin get_line(test_input); {update input file input_organization[test_input].mode:=reading; actual_input:=test_input; test_input:=none; end; end

 @<Process the input@>= input_has_ended:=false; while not input_has_ended or (actual_input<>0) do process_line; if out_mode = post then {last line has been changed begin write(out_file,xchr["@"]); write_ln(out_file,xchr["z"]); end;

 At the end of the program, we will tell the user if the change file had a line that didn’t match any relevant line in the master file.

@<Check that all changes have been read@>= for i:=1 to max_file_index do {all change files if input_organization[i].mode <> ignore then err_print(’! Change file entry did not match’)(i); .Change file entry ...@>


The main program.

\noindent Here is where \.{TIE starts, and where it ends. If a command line interface is present, changes to initialize the access might be needed here. ŝystem dependencies@> ĉommand line processing@>

@p begin initialize; print_ln(banner); {print a “banner line” print_ln(copyright); {include the copyright notice @<Initialize the input system@>; @<Read master file name@>; open_input; @<Change or master file producing@>; @<Process the input@>; @<Check that all changes have been read@>; end_of_TIE: {here files should be closed if the operating system requires it @<Print the job |history|@>; end.

 Some implementations may wish to pass the |history| value to the operating system so that it can be used to govern whether or not other programs are started. Here we simply report the history to the user. ŝystem dependencies@>

@<Print the job |history|@>= case history of spotless: print_nl(’(No errors were found.)’); troublesome: print_nl(’(Pardon me, but I think I spotted something wrong.)’); fatal: print_nl(’(That was a fatal error, my friend.)’); end {there are no other cases


System-dependent changes.

\noindent This section should be replaced, if necessary, by changes to the program that are necessary to make \.{TIE work at a particular installation. It is usually best to design your change file so that all changes to previous modules preserve the module numbering; then everybody’s version will be consistent with the printed program. More extensive changes, which introduce new modules, can be inserted here; then only the index itself will get a new module number. ŝystem dependencies@>


Index.

\noindent Here is the cross-reference table for the \.{TIE processor.


This document was generated on January 31, 2023 using texi2html 5.0.